package org.zhuzx.util;

import java.math.BigDecimal;
import java.util.*;

/**
 * 列表文本数据处理工具类，专门针对文本型ListMap数据。
 * @author	zhuzx	2016年10月10日
 */
public class ListTextDataUtil {

	/**
	 * 从Map列表中获取指定键的值组成的数组
	 */
	public static String[] getValues(List<Map<String,String>> list, String key) {
		String[] values = new String[list.size()];
		for (int i=0; i<list.size(); i++) {
			Map<String,String> row = list.get(i);
			values[i] = row.get(key);
		}
		return values;
	}
	
	/**
	 * 根据指定键获取与指定值相等的所有记录，返回这个新的子列表。
	 * @param ordered 母列表是否已按指定键排序
	 */
	@Deprecated//已排序的分支
	public static List<Map<String,String>> getSubList(List<Map<String,String>> list,
			String key, String value, boolean ordered) {
		List<Map<String,String>> subList = new ArrayList<>();
		for (Map<String,String> map : list) {
			if (map.get(key).equals(value)) subList.add(map);
		}
		return subList;
	}
	
	/**
	 * 根据指定键获取在两个值之间的所有记录，返回这个新的子列表。
	 * @param gtValue 左边界，包含，可为空。
	 * @param ltValue 右边界，包含，可为空。
	 * @param ordered 母列表是否已按指定键排序
	 */
	@Deprecated//未排序的分支
	public static List<Map<String,String>> getSubList(List<Map<String,String>> list,
			String key, String gtValue, String ltValue, boolean ordered) {
		String[] values = getValues(list, key);
		int leftMargin = 0;
		int rightMargin = list.size()-1;
		if (gtValue != null) {
			leftMargin = Arrays.binarySearch(values, gtValue);
			leftMargin = (leftMargin<0) ? (-leftMargin-1) : leftMargin;
		}
		if (ltValue != null) {
			rightMargin = Arrays.binarySearch(values, ltValue);
			rightMargin = (rightMargin<0) ? (-rightMargin-2) : rightMargin;
		}
		return list.subList(leftMargin, rightMargin+1);
	}
	
	/**
	 * 将一条记录根据指定键寻找合适位置，插入有序的列表中。
	 * @param list 列表必须已按该键排序
	 */
	@Deprecated//处理冲突，并且数值类型以String表示时不可直接作为排序依据。
	public static void insertInSequence(List<Map<String,String>> list, String key, Map<String,String> rec) {
		if (list.size() == 0) {
			list.add(rec);
			return;
		}
		String[] vals = getValues(list, key);
		int index = Arrays.binarySearch(vals, rec.get(key));
		index = (index<0) ? (-index-1) : index;
		list.add(index, rec);
	}
	
	/**
	 * 从列表中剔除指定键的值重复的记录，可指定多个键，若多个值均重复则剔除。<br>
	 * 相同记录只保留列表中最前面的一条。
	 */
	public static void excludeDuplicateRecord(List<Map<String,String>> list, String... keys) {
		Set<String> set = new HashSet<>();
		Iterator<Map<String,String>> ite = list.iterator();
		while (ite.hasNext()) {
			Map<String,String> map = ite.next();
			String uniqueValue = "";
			for (String aKey : keys) {
				uniqueValue += "," + map.get(aKey);
			}
			uniqueValue = uniqueValue.substring(1);
			if (set.contains(uniqueValue)) {
				ite.remove();
			} else {
				set.add(uniqueValue);
			}
		}
	}
	
	/**
	 * 从ListMap中提取指定字段值的合并结果
	 * @param list 基础数据来源
	 * @param combField 需要合并值的字段
	 * @param separator 值与值之间的分隔符
	 * @return 每条记录中对应字段值的拼接结果
	 */
	public static String getCombinedValues(List<Map<String,String>> list,
			String combField, String separator) {
		if (list.size() == 0) return null;
		StringBuilder sb = new StringBuilder();
		for (Map<String,String> map : list) {
			sb.append(map.get(combField) + separator);
		}
		sb.delete(sb.length()-separator.length(), sb.length());
		return sb.toString();
	}
	
	/**
	 * 执行mysql的group_concat操作，参数详细说明请参见内部调用的重载方法。
	 */
	public static List<Map<String,String>> groupConcat(List<Map<String,String>> list,
			String combField, String separator, String nullValueRep, String groupOrderBy, String... groupBy) {
		return groupConcat(list, new String[]{combField}, new String[]{separator},
				new String[]{nullValueRep}, groupOrderBy, groupBy);
	}
	
	/**
	 * 执行mysql的group_concat操作，参数详细说明请参见内部调用的重载方法。
	 */
	public static List<Map<String,String>> groupConcat(List<Map<String,String>> list,
			String[] combFieldArray, String separator, String nullValueRep, String groupOrderBy, String... groupBy) {
		String[] separatorArray = new String[combFieldArray.length];
		String[] nullValueRepArray = new String[combFieldArray.length];
		for (int i = 0; i<combFieldArray.length; i++) {
			separatorArray[i] = separator;
			nullValueRepArray[i] = nullValueRep;
		}
		return groupConcat(list, combFieldArray, separatorArray, nullValueRepArray, groupOrderBy, groupBy);
	}
	
	/**
	 * 执行mysql的group_concat操作
	 * @param list 原始数据
	 * @param combFieldArray 需要执行合并的字段，注意，传入的三个Array必须长度相等，否则返回空集。
	 * @param separatorArray 值与值之间的分隔符
	 * @param nullValueRepArray 若为空值时的代替值（想用null占位可传入"null"，若传入null或者""将导致值数量少于记录数）
	 * @param groupOrderBy 组内排序依据
	 * @param groupBy 分组依据，可多选。
	 * @return 新的列表，每条记录以组内第一条为基础，将指定字段替换为合并值。
	 */
	public static List<Map<String,String>> groupConcat(List<Map<String,String>> list,
			String[] combFieldArray, String[] separatorArray, String[] nullValueRepArray,
			String groupOrderBy, String... groupBy) {
		if (GeneralUtil.isEmpty(list) || combFieldArray.length!=separatorArray.length ||
				separatorArray.length!=nullValueRepArray.length) return new ArrayList<>();
		Comparator<Map<String,String>> comp = GeneralUtil.createMapComparator("", groupBy);
		Comparator<Map<String,String>> orderComp = null;
		if (groupOrderBy != null) {
			String[] orderArray = new String[groupBy.length+1];
			System.arraycopy(groupBy, 0, orderArray, 0, groupBy.length);
			orderArray[orderArray.length-1] = groupOrderBy;
			orderComp = GeneralUtil.createMapComparator("", orderArray);
		} else {
			orderComp = comp;
		}
		Collections.sort(list, orderComp);
		List<Map<String,String>> combinedlist = new ArrayList<>();
		Iterator<Map<String,String>> ite = list.iterator();
		Map<String,String> mergeInto = ite.next();
		while (ite.hasNext()) {
			Map<String,String> row = ite.next();
			boolean atSameGroup = comp.compare(mergeInto,row) == 0;
			for (int i=0; i<combFieldArray.length; i++) {
				String combField = combFieldArray[i];
				String nullValueRep = nullValueRepArray[i];
				if (atSameGroup) {
					String priValue = mergeInto.get(combField);
					priValue = (priValue==null) ? nullValueRep : priValue;
					String subValue = row.get(combField);
					subValue = (subValue==null) ? nullValueRep : subValue;
					String combValue = GeneralUtil.concatStringExcludeBlank(
							separatorArray[i], priValue, subValue);
					mergeInto.put(combField, combValue);
				} else {
					if (mergeInto.get(combField)==null && nullValueRep!=null) {
						mergeInto.put(combField, nullValueRep);
					}
				}
			}
			if (!atSameGroup) {
				combinedlist.add(mergeInto);
				mergeInto = row;
			}
		}
		combinedlist.add(mergeInto);//末尾行，无论是否单独一个组都未被上面的循环加入列表。
		return combinedlist;
	}
	
	/**
	 * 将两个具备严格对应关系的合并值重新分割后进行映射合并，各个映射合并结果再做一次普通合并，得到一个二维的合并值。<br>
	 * 空字段值会导致group by的对应关系产生混乱，造成错位或者长度不一致，慎用本方法。
	 */
	@Deprecated//应用场景太狭窄
	public static void combineFieldsBySplit(List<Map<String,String>> list,
			String prevField, String prevSep, String nextField, String nextSep,
			String mergeAs, String connector, String newSep) {
		for (Map<String,String> rec : list) {
			String[] prevTemp = rec.get(prevField).split(prevSep, -1);
			String[] nextTemp = rec.get(nextField).split(nextSep, -1);
			StringBuilder sb = new StringBuilder();
			for (int i=0; i<prevTemp.length; i++) {
				sb.append(newSep + prevTemp[i] + connector + nextTemp[i]);
			}
			sb.delete(0, newSep.length());
			rec.put(mergeAs, sb.toString());
		}
		ListDataUtil.removeKey(list, prevField, nextField);
	}
	
	/**
	 * 将两个字段值以映射形式合并为一个新值，新值的文本被分隔符分为两部分，前半部分是键，后半部分是值。
	 * @param list 原始数据
	 * @param prevField 映射关系中的键极所用的文本（来源于原始数据中的某个值）取自原始数据中此字段
	 * @param prevNullValueRep 若取得的值为空时，用于表示键极的替代文本。
	 * @param nextField 以上对应的值极
	 * @param nextNullValueRep 以上对应的值极
	 * @param mergeAs 映射合并值以单独字段表示
	 * @param connector 映射合并的新值以分隔符连接键极与值极，分隔符左边是键，右边是值。
	 */
	public static void combineFields(List<Map<String,String>> list,
			String prevField, String prevNullValueRep, String nextField, String nextNullValueRep,
			String mergeAs, String connector) {
		for (Map<String,String> rec : list) {
			String prevValue = rec.get(prevField);
			prevValue = (prevValue==null) ? prevNullValueRep : prevValue;
			String nextValue = rec.get(nextField);
			nextValue = (nextValue==null) ? nextNullValueRep : nextValue;
			String combValue = GeneralUtil.isBlank(connector) ?
					(prevValue+nextValue) : (prevValue+connector+nextValue);
			rec.put(mergeAs, combValue);
			rec.remove(prevField);
			rec.remove(nextField);
		}
	}
	
	/**
	 * 聚合类型定义，每种类型实现两个简单操作。<br>
	 * 注意：只支持整数，且不支持avg。
	 */
	private static enum AggregateType {
		COUNT {
			long getInitialValue(String fieldValue) {
				return GeneralUtil.isBlank(fieldValue) ? 0 : 1;
			}
		},
		SUM,
		MAX {
			long addGroupMember(long groupValue, String fieldValue) {
				if (GeneralUtil.isBlank(fieldValue)) {
					return groupValue;
				} else {
					long value = Long.parseLong(fieldValue);
					return (groupValue > value) ? groupValue : value;
				}
			}
		},
		MIN {
			long addGroupMember(long groupValue, String fieldValue) {
				if (GeneralUtil.isBlank(fieldValue)) {
					return groupValue;
				} else {
					long value = Long.parseLong(fieldValue);
					return (groupValue < value) ? groupValue : value;
				}
			}
		};
		/**
		 * 组内当前没有其它记录时，将这个值转化为聚合结果并返回。
		 */
		long getInitialValue(String fieldValue) {
			return GeneralUtil.isBlank(fieldValue) ? 0 : Long.parseLong(fieldValue);
		}
		/**
		 * 组内已有一个当前的聚合结果时，将这个值加入组，重新计算聚合结果并返回。
		 */
		long addGroupMember(long groupValue, String fieldValue) {
			return groupValue + getInitialValue(fieldValue);
		}
	}
	
	/**
	 * 对指定字段执行聚合操作，本方法仅支持整型。ListDataUtil中已提供了更加通用的aggregateField方法，本方法仅作为针对整型的备选方案。
	 * @param list 原始数据
	 * @param aggregateType 聚合类型，传入count、sum、max等文本，不区分大小写。
	 * @param field 要聚合的字段名
	 * @param asLabel 聚合结果值对应的新字段名
	 * @param putInEveryRow 是否在组内每条记录中都放入聚合结果，即put一个新键值对（传false则仅组内第一条记录）。
	 * @param groupBy 分组依据
	 */
	public static void aggregateField(List<Map<String,String>> list, String aggregateType,
			String field, String asLabel, boolean putInEveryRow, String... groupBy) {
		if (GeneralUtil.isEmpty(list)) return;
		Comparator<Map<String,String>> comp = GeneralUtil.createMapComparator("", groupBy);
		Collections.sort(list, comp);
		Iterator<Map<String,String>> ite = list.iterator();
		Map<String,String> groupFirst = ite.next();
		AggregateType aggr = AggregateType.valueOf(aggregateType.toUpperCase());
		long groupValue = aggr.getInitialValue(groupFirst.get(field));
		while (ite.hasNext()) {
			Map<String,String> row = ite.next();
			boolean atSameGroup = comp.compare(groupFirst,row) == 0;
			if (atSameGroup) {
				groupValue = aggr.addGroupMember(groupValue, row.get(field));
			} else {
				groupFirst.put(asLabel, String.valueOf(groupValue));
				groupFirst = row;
				groupValue = aggr.getInitialValue(groupFirst.get(field));
			}
		}
		groupFirst.put(asLabel, String.valueOf(groupValue));//末尾行
		
		if (putInEveryRow) {
			ite = list.iterator();
			groupFirst = ite.next();
			while (ite.hasNext()) {
				Map<String,String> row = ite.next();
				boolean atSameGroup = comp.compare(groupFirst,row) == 0;
				if (atSameGroup) {
					row.put(asLabel, groupFirst.get(asLabel));
				} else {
					groupFirst = row;//末尾行若在单独的组则已有值，不需要处理。
				}
			}
		}
	}
	
	/**
	 * 计数将指定字段的值分割后得到的文本数量
	 * @param list 原始数据
	 * @param field 需要计数的字段名
	 * @param asLabel 用于存放计数结果的新字段名
	 * @param separator 分割符
	 */
	public static void countConcatedField(List<Map<String,String>> list,
			String field, String asLabel, String separator) {
		for (Map<String,String> row : list) {
			if (GeneralUtil.isBlank(row.get(field))) {
				row.put(asLabel, "0");
			} else {
				row.put(asLabel, String.valueOf(row.get(field).split(separator).length));
			}
		}
	}
	
	/**
	 * 返回由最小值和最大值两个元素组成的数组，不需要list有序。
	 * @param list 目标列表，不管是否已根据field排序都能提取。
	 * @param field 需要取值的键名
	 * @param isNumber 是否数值类型，若需要比较数值大小时，则将键值转化后再比较，以避免字符串比较时位数不一致问题。
	 */
	public static String[] getMinMaxValue(List<Map<String,String>> list, String field, boolean isNumber) {
		if (list == null) return null;
		String[] result = new String[2];
		if (list.size() == 0) return result;
		String[] values = getValues(list, field);
		if (isNumber) {
			Comparator<String> comp = new Comparator<String>() {
				public int compare(String o1, String o2) {
					return (new BigDecimal(o1)).compareTo(new BigDecimal(o2));
				}
			};
			Arrays.sort(values, comp);
		} else {
			Arrays.sort(values);
		}
		result[0] = values[0];
		result[1] = values[values.length-1];
		return result;
	}
	
	/**
	 * 根据list的每条记录中具备映射关系的两个字段值生成数据字典，存储记录的map内部必须是有序的，且所有map必须拥有一致的键。<br>
	 * 注意：本方法不执行上述检查，所以需要参数list本身保证业务正确性。
	 * @param list 每条记录只取前两个字段的值，第一个字段值作为键，第二个字段值作为值。
	 * @return 有序的map，按list的元素顺序排列。
	 */
	public static Map<String,String> createDataDictionaryByTwoField(List<Map<String,String>> list) {
		Map<String, String> result = new LinkedHashMap<>();
		if (list == null) throw new IllegalArgumentException();
		if (list.size() == 0) return result;
		for (Map<String, String> map : list) {
			String key = null;
			for (Map.Entry<String, String> entry : map.entrySet()) {
				if (key == null) {
					key = entry.getValue();
				} else {
					result.put(key, entry.getValue());
					break;//只读取前面两列
				}
			}
		}
		return result;
	}
	
	/**
	 * 去除指定字段值中的毫秒数
	 * @param list 原始数据
	 * @param fields 原始类型必须是Timestamp
	 */
	public static void deleteDecimalPointOfTimestamp(List<Map<String,String>> list, String... fields) {
		deleteLowerDigitsOfTimestamp(list, 19, fields);
	}
	
	/**
	 * 截取指定字段值，只保留指定位数
	 * @param list 原始数据
	 * @param keepLength 保留位数
	 * @param fields 原始类型必须是Timestamp
	 */
	public static void deleteLowerDigitsOfTimestamp(List<Map<String,String>> list, int keepLength, String... fields) {
		for (Map<String,String> row : list) {
			for (String field : fields) {
				if (!GeneralUtil.isBlank(row.get(field))) {
					row.put(field, row.get(field).substring(0, keepLength));
				}
			}
		}
	}
	
	/**
	 * 去除指定字段值的小数部分
	 */
	@SuppressWarnings("unused")
	private static void deleteDecimalPoint(List<Map<String,String>> list, String... fields) {
		for (Map<String,String> row : list) {
			for (String field : fields) {
				if (!GeneralUtil.isBlank(row.get(field))) {
					String value = row.get(field);
					int index = value.lastIndexOf(".");
					if (index >= 0) {
						row.put(field, value.substring(0, index));
					}
				}
			}
		}
	}

	/**
	 * 根据二维数组生成ListMap，第一个子数组为键，其余为值。
	 */
	public static List<Map<String,String>> createListMap(String[][] array) {
		if (array == null || array.length < 2) {
			throw new IllegalArgumentException("array参数至少要有2个元素！");
		}
		List<Map<String,String>> list = new ArrayList<>(array.length-1);
		String[] keys = array[0];
		for (int i=1; i<array.length; i++) {
			String[] values = array[i];
			Map<String,String> map = new HashMap<>();
			for (int j=0; j<keys.length; j++) {
				map.put(keys[j], values[j]);
			}
			list.add(map);
		}
		return list;
	}

}